package cn.dansj.common.request.configure;

import cn.dansj.common.processor.TargetType;
import cn.dansj.common.processor.meta.WriteToResource;
import cn.dansj.common.utils.transfer.ArrayUtils;
import cn.dansj.common.utils.transfer.Transformation;

import javax.servlet.Servlet;
import javax.websocket.server.ServerEndpoint;

import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;

import java.util.*;

@WriteToResource(type = TargetType.Imports, keyProperty = "org.springframework.boot.autoconfigure.AutoConfiguration")
public class Endpoints {
    public static final List<RequestMapping> requestMappingList = new ArrayList<>();

    public Endpoints(ApplicationContext applicationContext) {
        ApplicationContext target = applicationContext;
        while (target != null) {
            //获取所有websocket连接
            requestMappingList.addAll(getWebSocketAddresses(target));
            //获取所有http连接
            requestMappingList.addAll(mappingsForContext(target));
            target = target.getParent();
        }
    }

    private Set<RequestMapping> getWebSocketAddresses(ApplicationContext applicationContext) {
        Set<RequestMapping> webSocketAddresses = new HashSet<>();
        // 遍历所有被Spring管理的Bean
        for (String beanName : applicationContext.getBeanDefinitionNames()) {
            Class<?> beanClass = applicationContext.getType(beanName);
            // 检查Bean是否带有ServerEndpoint注解
            if (beanClass != null && beanClass.isAnnotationPresent(ServerEndpoint.class)) {
                final ServerEndpoint serverEndpoint = beanClass.getAnnotation(ServerEndpoint.class);
                webSocketAddresses.add(new RequestMapping(serverEndpoint.value(), RequestMethod.GET, true));
            }
        }

        return webSocketAddresses;
    }

    // RequestMapping注解的默认开放GET和POST方法, 其他全部禁用返回404
    private static final List<RequestMethod> defaultMethods = ArrayUtils.asList(RequestMethod.GET, RequestMethod.POST);

    private List<RequestMapping> mappingsForContext(ApplicationContext applicationContext) {
        List<RequestMapping> requestMappingList = new ArrayList<>();
        final Map<String, List<HandlerMapping>> mappings = describeMappings(applicationContext);
        for (String key : mappings.keySet()) {
            for (HandlerMapping mapping : mappings.get(key)) {
                if (mapping instanceof RequestMappingInfoHandlerMapping) {
                    RequestMappingInfoHandlerMapping requestMappingInfoHandlerMapping = (RequestMappingInfoHandlerMapping) mapping;
                    final Set<RequestMappingInfo> requestMappingInfos = requestMappingInfoHandlerMapping.getHandlerMethods().keySet();
                    for (RequestMappingInfo requestMappingInfo : requestMappingInfos) {
                        for (String urlPattern : requestMappingInfo.getPatternValues()) {
                            for (RequestMethod requestMethod : Transformation.nvl(requestMappingInfo.getMethodsCondition().getMethods(), defaultMethods)) {
                                requestMappingList.add(new RequestMapping(urlPattern, requestMethod));
                            }
                        }
                    }
                }
            }
        }
        return requestMappingList;
    }

    public Map<String, List<HandlerMapping>> describeMappings(ApplicationContext context) {
        if (context instanceof WebApplicationContext) {
            WebApplicationContext webApplicationContext = (WebApplicationContext) context;
            return describeMappings(webApplicationContext);
        }
        return Collections.emptyMap();
    }

    private Map<String, List<HandlerMapping>> describeMappings(WebApplicationContext context) {
        Map<String, List<HandlerMapping>> mappings = new HashMap<>();
        determineDispatcherServlets(context).forEach((name, dispatcherServlet) -> mappings.put(name, describeMappings(new DispatcherServletHandlerMappings(name, dispatcherServlet, context))));
        return mappings;
    }

    private Map<String, DispatcherServlet> determineDispatcherServlets(WebApplicationContext context) {
        Map<String, DispatcherServlet> dispatcherServlets = new LinkedHashMap<>();
        context.getBeansOfType(ServletRegistrationBean.class).values().forEach((registration) -> {
            Servlet servlet = registration.getServlet();
            if (servlet instanceof DispatcherServlet && !dispatcherServlets.containsValue(servlet)) {
                dispatcherServlets.put(registration.getServletName(), (DispatcherServlet) servlet);
            }
        });
        context.getBeansOfType(DispatcherServlet.class).forEach((name, dispatcherServlet) -> {
            if (!dispatcherServlets.containsValue(dispatcherServlet)) {
                dispatcherServlets.put(name, dispatcherServlet);
            }
        });
        return dispatcherServlets;
    }

    private List<HandlerMapping> describeMappings(DispatcherServletHandlerMappings mappings) {
        return mappings.getHandlerMappings();
    }

    public static class RequestMapping {
        private final String urlPattern;
        private final RequestMethod method;
        private final boolean websocket;

        public RequestMapping(String urlPattern, RequestMethod method, boolean websocket) {
            this.urlPattern = urlPattern;
            this.method = method;
            this.websocket = websocket;
        }

        public RequestMapping(String urlPattern, RequestMethod method) {
            this(urlPattern, method, false);
        }

        public String getUrlPattern() {
            return urlPattern;
        }

        public RequestMethod getMethod() {
            return method;
        }

        public boolean isWebsocket() {
            return websocket;
        }

        @Override
        public String toString() {
            return "[" + method + "] <" + urlPattern + ">" + (websocket ? " websocket" : "");
        }
    }
}